package com.niiwoo.civet.account.service.local.openAccount;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.fastjson.JSON;
import com.niiwoo.civet.account.constant.AccountRegisterErrorCode;
import com.niiwoo.civet.account.constant.MQConstant;
import com.niiwoo.civet.account.dao.entity.AccountBase;
import com.niiwoo.civet.account.dao.entity.ActivityOrder;
import com.niiwoo.civet.account.dao.entity.BankCard;
import com.niiwoo.civet.account.dao.entity.BankType;
import com.niiwoo.civet.account.dao.entity.OpenAccountRecord;
import com.niiwoo.civet.account.dto.Tuple2;
import com.niiwoo.civet.account.dto.request.UserIdentityDataMQDTO;
import com.niiwoo.civet.account.enums.AccountTypeEnum;
import com.niiwoo.civet.account.enums.OpenAccountRecordStatusEnum;
import com.niiwoo.civet.account.enums.OpenAccountWayEnum;
import com.niiwoo.civet.account.service.MarketingAsyncDubboService;
import com.niiwoo.civet.account.service.local.BankCardService;
import com.niiwoo.civet.account.service.local.BankTypeService;
import com.niiwoo.civet.account.service.local.MarketingLocalService;
import com.niiwoo.civet.account.service.local.OldAccountBalanceService;
import com.niiwoo.civet.account.service.local.OpenAccountRecordService;
import com.niiwoo.tripod.base.component.PrivacyMasks;
import com.niiwoo.tripod.base.exception.VerifyFailedException;
import com.niiwoo.tripod.lanmao.component.LanMaoDirectService;
import com.niiwoo.tripod.lanmao.enums.UserAuthorizationEnum;
import com.niiwoo.tripod.lanmao.enums.UserRoleEnum;
import com.niiwoo.tripod.lanmao.request.QueryUserInformationRequest;
import com.niiwoo.tripod.lanmao.response.QueryUserInformationResponse;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
//@Component
public class OpenAccountTemplate {

    /**
     * 角色权限
     */
    private static final Map<UserRoleEnum, Set<String>> AUTH_LIST_MAP = new HashMap<>();

    static {
        Set<String> s2 = new HashSet<>();
        s2.add(UserAuthorizationEnum.REPAYMENT.name());
        s2.add(UserAuthorizationEnum.RECHARGE.name());
        s2.add(UserAuthorizationEnum.WITHDRAW.name());
        AUTH_LIST_MAP.put(UserRoleEnum.INVESTOR, Collections.singleton(UserAuthorizationEnum.TENDER.name()));
        AUTH_LIST_MAP.put(UserRoleEnum.BORROWERS, Collections.unmodifiableSet(s2));
        AUTH_LIST_MAP.put(UserRoleEnum.GUARANTEECORP, Collections.singleton(UserAuthorizationEnum.COMPENSATORY.name()));
    }

    @Autowired
    OpenAccountRecordService openAccountRecordService;
    @Autowired
    BankCardService bankCardService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    BankTypeService bankTypeService;

    @Autowired
    PrivacyMasks privacyMasks;

    @Autowired
    SnowflakeIdWorker snowflakeIdWorker;

    @Autowired
    LanMaoDirectService lanMaoDirectService;

    @Autowired
    OldAccountBalanceService oldAccountBalanceService;

    @Autowired
    MarketingLocalService marketingLocalService;

    @Reference(version = "1.0.0")
    private MarketingAsyncDubboService marketingAsyncDubboService;

    /**
     * 获取银行卡类型ID
     *
     * @param bankCode 银行代码
     */
    public long getBankTypeId(String bankCode) {
        return Optional.ofNullable(bankTypeService.getBankTypeByCode(bankCode))
                .map(BankType::getId)
                .orElseThrow(() -> new BizException(AccountRegisterErrorCode.UNKNOWN_BANK_TYPE_CODE));
    }

    /**
     * 根据角色获取账户权限
     *
     * @param accountRole 角色
     */
    public String getAuthTextByRole(UserRoleEnum accountRole) {
        return String.join(",", getAuthSetByRole(accountRole));
    }

    /**
     * 根据角色获取账户权限
     *
     * @param accountRole 角色
     */
    public Set<String> getAuthSetByRole(UserRoleEnum accountRole) {
        return Optional.ofNullable(AUTH_LIST_MAP.get(accountRole)).map(set -> (Set<String>)new HashSet<>(set)).orElseThrow(() -> new BizException(AccountRegisterErrorCode.NOT_SUPPORT_ROLE));
    }

    /**
     * 查询懒猫账户状态
     */
    public QueryUserInformationResponse queryLanmaoAccountStatus(String accountNo, String requestNo) {
        QueryUserInformationRequest request = new QueryUserInformationRequest();
        request.setPlatformUserNo(accountNo);
        request.setRequestNo(requestNo);
        QueryUserInformationResponse response;
        try {
            log.info("懒猫接口###查询用户信息接口###request={}", JSON.toJSONString(request));
            response = lanMaoDirectService.queryUserInformation(request).get();
            log.info("懒猫接口###查询用户信息接口###response={}", JSON.toJSONString(response));
            return response;
        } catch (ExecutionException e) {
            if (e.getCause() != null && e.getCause() instanceof VerifyFailedException) {
                log.error(String.format("懒猫接口###查询用户信息接口###验签失败, requestNo=%s,  exceptionMessage=%s", request.getRequestNo(), e.getMessage()), e);
                throw new BizException(AccountRegisterErrorCode.LANMAO_QUERY_EXCEPTION);
            } else {
                // 未知异常
                log.error(String.format("懒猫接口###查询用户信息接口###未知异常1， requestNo=%s, exceptionMessage=%s", request.getRequestNo(), e.getMessage()), e);
                throw new BizException(AccountRegisterErrorCode.LANMAO_QUERY_EXCEPTION);
            }
        } catch (Exception e) {
            // 未知异常
            log.error(String.format("懒猫接口###查询用户信息接口###未知异常2， requestNo=%s, exceptionMessage=%s", request.getRequestNo(), e.getMessage()), e);
            throw new BizException(AccountRegisterErrorCode.LANMAO_QUERY_EXCEPTION);
        }
    }

    /**
     * 更新成功订单模板
     */
    boolean doHandleSuccessOrder(OpenAccountRecord record, String groupAccount, Supplier<Boolean> s1, boolean isInitAccount, AccountBase updateBase, Supplier<Boolean> s2, Supplier<Boolean> s3) {
        return OpenAccountRecordStatusEnum.except(record.getStatus(), OpenAccountRecordStatusEnum.SUCCESS) ||
                doHandle(
                        record,
                        () -> openAccountRecordService.saveRecordAndAccountOrderAndAccountInfo(AccountEntityBuilder.buildUpdateOpenAccountRecord(OpenAccountRecordStatusEnum.SUCCESS, record, groupAccount), s1, isInitAccount, updateBase, s2, s3),
                        () -> {
                            returnOldAccountBalance(record);
                            notifyUCIfNecessary(record, Optional.ofNullable(bankCardService.getNewestSimpleUserBankCard(record.getUserId())));
                            notifyR360IfNecessary(record, true, "银行卡绑定成功");
                            return null;
                        }
                );
    }

    /**
     * 更新非成功订单模板
     */
    boolean doHandleUnSuccessOrder(OpenAccountRecord record, OpenAccountRecordStatusEnum recordStatus, Supplier<Boolean> s, String errorCode, String errorMessage) {
        return OpenAccountRecordStatusEnum.except(record.getStatus(), recordStatus) ||
                doHandle(
                        record,
                        () -> openAccountRecordService.saveRecordAndAccountOrderIfPossible(AccountEntityBuilder.buildUpdateOpenAccountRecord(recordStatus, record, ""), s),
                        () -> {
                            if (recordStatus == OpenAccountRecordStatusEnum.FAILURE) {
                                notifyR360IfNecessary(record, false, String.format("银行卡绑定失败: %s-%s", StringUtils.defaultIfBlank(errorCode, ""), StringUtils.defaultIfBlank(errorMessage, "")));
                            }
                            return null;
                        }
                );
    }

    /**
     * 银行卡更新函数
     */
    Supplier<Boolean> getUpdateBankFunction(BankCard preBankCard, String userId, Long accountId, AccountTypeEnum accountType, String name, String idCardNo, String bankCardNo, String bankCode, String mobile) {
        Optional<BankCard> preBankCardOpt = Optional.ofNullable(preBankCard);
        String encryptBankCardNo = privacyMasks.encryptPrivacy(bankCardNo);
        String encryptIdCardNo = privacyMasks.encryptPrivacy(idCardNo);
        String encryptMobile = privacyMasks.encryptPrivacy(mobile);
        return () -> {
            boolean isMatch = preBankCardOpt.map(b -> b.getRealName().equals(name) && b.getIdCard().equals(encryptIdCardNo) && b.getBankAccountNo().equals(encryptBankCardNo) && b.getMobileNo().equals(encryptMobile)).orElse(false);
            // 是否更新银行卡 --> 存在且四要素匹配则更新
            boolean isUpdate = preBankCardOpt.isPresent() && isMatch;
            // 是否标记删除老卡
            boolean isDelOldOne = preBankCardOpt.isPresent() && !isMatch;
            BankCard updateCard;
            if (isUpdate) {
                updateCard = AccountEntityBuilder.buildUpdateBankCard(preBankCardOpt.get(),
                                                                        encryptBankCardNo,
                                                                        getBankTypeId(bankCode),
                                                                        bankCode,
                                                                        encryptMobile,
                                                                        name,
                                                                        encryptIdCardNo);
            } else {
                updateCard = AccountEntityBuilder.buildInitBankCard(
                                                                snowflakeIdWorker.nextId(),
                                                                getBankTypeId(bankCode),
                                                                userId,
                                                                accountId,
                                                                accountType.getValue(),
                                                                name,
                                                                encryptIdCardNo,
                                                                encryptBankCardNo,
                                                                bankCode,
                                                                encryptMobile);
            }
            if (isDelOldOne) {
                BankCard delCard = new BankCard();
                delCard.setId(preBankCardOpt.get().getId());
                delCard.setDelFlag((byte) 1);
                delCard.setUpdateTime(new Date());
                bankCardService.updateBankCard(delCard);
            }
            return isUpdate ? bankCardService.updateBankCard(updateCard) : bankCardService.saveBankCard(updateCard);
        };
    }

    /**
     * 更新订单
     *
     * @param s1 订单更新函数
     * @param s2 后置处理函数
     */
    boolean doHandle(OpenAccountRecord record, Supplier<Tuple2<Boolean, Boolean>> s1, Supplier<Void> s2) {
        Tuple2<Boolean, Boolean> tuple2 = s1.get();
        if (tuple2.get_1()) {
            if (tuple2.get_2()) {
                s2.get();
            }
        } else {
            log.error("更新开户记录或订单###失败, record={}", JSON.toJSONString(record));
        }
        return tuple2.get_1();
    }


    /**-------------------------------------------- 开户后置操作 --------------------------------------------**/

    /**
     * 返还老账户余额
     */
    private void returnOldAccountBalance(OpenAccountRecord record) {
        if (!OpenAccountWayEnum.except(record.getOpenAccountWay(), OpenAccountWayEnum.PERSON_BIND_BANK_CARD_REGISTER, OpenAccountWayEnum.R360_PERSON_BIND_BANK_CARD_REGISTER)) {
            return;
        }
        ActivityOrder order = oldAccountBalanceService.prepare(record.getUserId(), record.getAccountNo(), UserRoleEnum.enumOf(record.getAccountRole()));
        if (order == null) {
            return;
        }
        log.info("开户成功###返还老账户余额###请求, order={}", JSON.toJSONString(order));
        RpcContext.getContext().asyncCall(() -> {
            try {
                marketingAsyncDubboService.sendPrize(order.getRequestNo(), UserRoleEnum.enumOf(order.getAccountRole()));
                log.info("开户成功###返还老账户余额###请求发送成功, userId={}, orderId={}", order.getUserId(), order.getId());
            } catch (Exception e) {
                log.error(String.format("开户成功###返还老账户余额###异常, userId=%s, orderId=%s, errorMessage=%s", order.getUserId(), order.getId(), e.getMessage()), e);
            }
        });
    }


    /**
     * 使用 mq 同步数据至用户中心 1. 修改二要素、四要素状态 2. 姓名和身份证号
     *
     * 仅在初次开户成功时调用
     */
    private void notifyUCIfNecessary(OpenAccountRecord record, Optional<BankCard> cardOptional) {
        log.info("开户成功###准备发送用户中心的消息, record={}, isBankCardExists={}", JSON.toJSONString(record), cardOptional.isPresent());
        boolean isFirstTime = OpenAccountWayEnum.except(record.getOpenAccountWay(), OpenAccountWayEnum.PERSON_BIND_BANK_CARD_REGISTER, OpenAccountWayEnum.ACTIVATE, OpenAccountWayEnum.R360_PERSON_BIND_BANK_CARD_REGISTER, OpenAccountWayEnum.R360_ACTIVATE, OpenAccountWayEnum.ENTERPRISE_ACTIVATE);
        if (isFirstTime && cardOptional.isPresent()) {
            UserIdentityDataMQDTO identityDataMQDTO = new UserIdentityDataMQDTO();
            identityDataMQDTO.setUserId(record.getUserId());
            identityDataMQDTO.setName(cardOptional.get().getRealName());
            identityDataMQDTO.setIdCardNo(cardOptional.get().getIdCard());
            log.info("初次开户成功###同步二要素到用户中心, data={}", JSON.toJSONString(identityDataMQDTO));
            rabbitTemplate.convertAndSend(MQConstant.UC_IDENTITY_EXCHANGE, MQConstant.UC_IDENTITY_DATA_ROUTING, identityDataMQDTO);
        }
    }

    /**
     * 使用 mq 同步数据至R360, 使用条件 1. 融360开户记录 2. 借款账户
     *
     * @param record 开户记录
     * @param isSuccess 是否成功
     * @param reason 身份证号
     */
    private void notifyR360IfNecessary(OpenAccountRecord record, boolean isSuccess, String reason) {
        log.info("融360开户###准备发送R360的消息, isSuccess={}, record={}", isSuccess, JSON.toJSONString(record));
        boolean isR360Record = OpenAccountWayEnum.except(record.getOpenAccountWay(), OpenAccountWayEnum.R360_PERSON_BIND_BANK_CARD_REGISTER, OpenAccountWayEnum.R360_GROUP_ACCOUNT_CORRELATE, OpenAccountWayEnum.R360_ACTIVATE);
        boolean isBorrowAccount = UserRoleEnum.enumOf(record.getAccountRole()) == UserRoleEnum.BORROWERS;
        if (isR360Record && isBorrowAccount) {
            Map<String, String> mq = new HashMap<>();
            mq.put("niiwoo_user_id", String.valueOf(record.getUserId()));
            mq.put("bind_status", String.valueOf(isSuccess ? 1 : 2)); // bind_status  1：成功；2：失败；
            mq.put("reason", reason); // reason 失败显示具体原因；成功：银行卡绑定成功
            log.info("融360开户###同步状态至融360, data={}", JSON.toJSONString(mq));
            rabbitTemplate.convertAndSend(MQConstant.R360_OPEN_ACCOUNT_EXCHANGE, MQConstant.R360_OPEN_ACCOUNT_ROUTING, mq);
        }
    }

}
